﻿/*******************************************************************************
 * Chirpr Business Logic                          http://www.codeplex.com/chirpr
 *                                              Microsoft Public License (Ms-PL)
 * Authors
 *  - Charles Schmidt   2007-08  http://www.pickelhau.be
 *  - Michael Flanakin  2008-09  http://www.michaelflanakin.com
 *
 * chirpr
 *  - pages      : page constants
 *  - host       : host properties/functions
 *  - properties : properties and data access functions
 *  - twitter    : functions for interacting with twitter
 *  - init       : initialization event
 ******************************************************************************/

var chirpr = null;

// if this is NOT the main window, reference parent; otherwise, create a new gadget instance
if (window.opener != null || (typeof(System) != "undefined" && document.body != System.Gadget.document.body))
{
	chirpr = (window.opener != null) ? window.opener.chirpr : System.Gadget.document.chirpr;
	util.addHandler(window, "onload", function() { page.init(); });
}
else
{
	chirpr = 
	{
		/***************************************************************************
		 * chirpr.pages : standard page names chirpr uses
		 **************************************************************************/
		pages:
		{
			main     : 'main.htm',
			message  : 'message.htm',
			settings : 'settings.htm'
		},
		
		
		/***************************************************************************
		 * chirpr.properties
		 *  - get  : gets a property from the data store; parameters: <property>
		 *  - set* : sets a property in the data store; parameters: <prop>, <value>
		 *  - sync : syncs local property store with data store
		 **************************************************************************/
		properties:
		{
			//***| properties |*****************************************************
			
			userName       : 'tUserName',
			password       : 'tPassword',
			replyToUser    : 'toUser',
			replyToMessage : 'toMessage',
			displaySize    : 'displaySize',
			notifySound    : 'notifySound',
			
			defaultDisplaySize : 225,
			
			//***| public |*********************************************************
			
			setUserName       : function(v) { gadget.settings.set(chirpr.properties.userName      , v); chirpr.userName       = v; },
			setPassword       : function(v) { gadget.settings.set(chirpr.properties.password      , v); chirpr.password       = v; },
			setReplyToUser    : function(v) { gadget.settings.set(chirpr.properties.replyToUser   , v); chirpr.replyToUser    = v; },
			setReplyToMessage : function(v) { gadget.settings.set(chirpr.properties.replyToMessage, v); chirpr.replyToMessage = v; },
			setDisplaySize    : function(v) { gadget.settings.set(chirpr.properties.displaySize   , v); chirpr.displaySize    = v; },
			setNotifySound    : function(v) { gadget.settings.set(chirpr.properties.notifySound   , v); chirpr.notifySound    = v; },
			sync: function()
			{
				// if not in browser or don't have credentials, get creds from querystring
				if (gadget.host.sidebar || !chirpr.hasCredentials())
				{
					// this if block ensures we don't override dynamic settings with an empty querystring
					chirpr.userName = gadget.settings.get(chirpr.properties.userName);
					chirpr.password = gadget.settings.get(chirpr.properties.password);
				}
				
				// other
				chirpr.replyToUser    = gadget.settings.get(chirpr.properties.replyToUser);
				chirpr.replyToMessage = gadget.settings.get(chirpr.properties.replyToMessage);
				chirpr.displaySize    = gadget.settings.getInt(chirpr.properties.displaySize, chirpr.properties.defaultDisplaySize);
				chirpr.notifySound    = gadget.settings.get(chirpr.properties.notifySound);
				
				// check for debug flag
				// TODO: chirpr.properties.checkDebugFlag();
			}
		},
		
		
		/***************************************************************************
		 * chirpr.twitter
		 *  - signIn      : validates user credentials
		 *  - signOut     : ends a user session
		 *  - sendMessage : posts a new message to twitter
		 *  - getMessages : gets last 20 messages
		 **************************************************************************/
		twitter:
		{
			counter     : 0,
			lastMessage : 0,
			signInUrl   : "http://twitter.com/account/verify_credentials.xml",
			signOutUrl  : "http://twitter.com/account/end_session",
			getItemsUrl : "http://twitter.com/statuses/friends_timeline.json?",
			postItemUrl : "http://twitter.com/statuses/update.xml?",
			followUrlFormat : "http://twitter.com/friendships/create/{0}.json",
			
			signIn: function(uid, pwd)
			{
				chirpr.logAuth("chirpr", "chirpr.twitter.signIn", gadget.logging.entryPoint, true, "user="+uid);
				try
				{
					util.http.get(chirpr.twitter.signInUrl, uid, pwd);
				}
				catch(e)
				{
					// TODO: force condense this checkpoint
					chirpr.logAuth("chirpr", "chirpr.twitter.signIn", gadget.logging.exitPoint, false, "sign in failed for '"+uid+"': " + (util.error.isError(e) ? e.description : e));
					return false;
				}
				chirpr.logAuth("chirpr", "chirpr.twitter.signIn", gadget.logging.exitPoint, true, "credentials valid");
				return true;
			},
			
			signOut: function()
			{
				chirpr.logAuth("chirpr", "chirpr.twitter.signOut", gadget.logging.entryPoint);
				try
				{
					util.http.get(chirpr.twitter.signOutUrl);
				}
				catch(e)
				{
					// TODO: force condense this checkpoint
					chirpr.logAuth("chirpr", "chirpr.twitter.signOut", gadget.logging.exitPoint, false, "sign out failed: " + (util.error.isError(e) ? e.description : e));
					return false;
				}
				chirpr.logAuth("chirpr", "chirpr.twitter.signOut", gadget.logging.exitPoint, true, "credentials no longer cached");
				return true;
			},
			
			getMessages: function(getSuccess, getFailed)
			{
				// log
				chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", gadget.logging.entryPoint);
				
				// if offline, return test item
				if (gadget.host.offline)
				{
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", "getting offline messages");
					var items =
					[{
						in_reply_to_status_id : null,
						user:
						{
							description       : "[user description]",
							url               : "http:\/\/www.codeplex.com\/chirpr",
							name              : "Chirpie",
							"protected"       : false,
							profile_image_url : "http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/61847487\/chirpie_128x_bigger.png",
							screen_name       : "chirpr",
							followers_count   : 999999,
							location          : "The World",
							id                : 16724851
						},
						favorited             : false,
						in_reply_to_user_id   : null,
						truncated             : false,
						source                : "chirpr",
						id                    : 123456789,
						created_at            : "Thu Apr 28 19:40:05 +0000 2009",
						text                  : "I am @chirpr, hear me roar... #roar"
					},
					{
						in_reply_to_status_id : null,
						user:
						{
							description       : "[user description]",
							url               : "http:\/\/www.codeplex.com\/chirpr",
							name              : "Chirpie",
							"protected"       : false,
							profile_image_url : "http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/61847487\/chirpie_128x_bigger.png",
							screen_name       : "chirpr",
							followers_count   : 999999,
							location          : "The World",
							id                : 16724851
						},
						favorited             : false,
						in_reply_to_user_id   : null,
						truncated             : false,
						id                    : 123456789,
						source                : "chirpr",
						created_at            : "Thu Oct 16 23:40:05 +0000 2008",
						text                  : "@chirpr did you ever fix the unicode chars (i.e. \"ääkköstestiä\")?"
					},
					{
						in_reply_to_status_id : null,
						user:
						{
							description       : "[user description]",
							url               : "http:\/\/www.codeplex.com\/chirpr",
							name              : "Chirpie",
							"protected"       : false,
							profile_image_url : "http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/61847487\/chirpie_128x_bigger.png",
							screen_name       : "chirpr",
							followers_count   : 999999,
							location          : "The World",
							id                : 16724851
						},
						favorited             : false,
						in_reply_to_user_id   : null,
						truncated             : false,
						id                    : 123456789,
						source                : "chirpr",
						created_at            : "Thu Oct 16 2:40:05 +0000 2008",
						text                  : "this is an offline tweet using @chirpr from http://codeplex.com/chirpr... lovin' it!"
					}];
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", "calling success handler", true, "returning "+items.length+" messages");
					getSuccess(items);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", gadget.logging.exitPoint, true, items.length+" messages");
					return;
				}
				
				// if no credentials, return null
				if (!chirpr.hasCredentials()) { chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", gadget.logging.exitPoint, true, "(no credentials)"); return; }
				var sendFailed = function(http, e)
				{
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", gadget.logging.entryPoint, true, "http="+http+", e="+e);
					var err = -1;
					if (util.error.isError(e))
					{
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", "error: "+e.description);
						err = e.description;
					}
					else if (typeof(e) == "number")
					{
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", "error: "+e);
						err = e;
					}
					else if (http.responseText == null || http.responseText.length == 0)
					{
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", "response empty, http status: "+http.status);
						err = http.status;
					}
					else
					{
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", "getting error from response: "+http.responseText);
						var response = eval("("+http.responseText+")");
						if (response == null || typeof(response.error) == "undefined")
						{
							err = -999;
						}
						else if (response.error == "Rate limit exceeded. Clients may not make more than 100 requests per hour.")
						{
							err = 400;
						}
						else
						{
							err = response.error;
						}
					}
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", "calling failure handler ("+err+")");
					getFailed(err);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[failed]", gadget.logging.exitPoint, true, "error="+err);
				};
				var sendSuccess = function(http)
				{
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", gadget.logging.entryPoint, true, "extracting messages");
					try
					{
						var messages = eval("("+http.responseText+")");
						if (typeof(messages.length) == "undefined")
						{
							// TODO: figure out better way to log before/after external call when this is exit point
							chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", "response could not be parsed; response: "+http.responseText, false, "calling failure logic");
							sendFailed(http, "invalid response");
							chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", gadget.logging.exitPoint, false, "response could not be parsed; response: "+http.responseText);
							return;
						}
						if (messages.length > 0)
						{
							chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", "updating last message to "+messages[0].id);
							chirpr.twitter.lastMessage = messages[0].id;
						}
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", "retrieved "+messages.length+" messages", true, "calling success handler");
						getSuccess(messages);
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", gadget.logging.exitPoint, true, messages.length+" messages");
						return messages.length;
					}
					catch (e)
					{
						// TODO: figure out better way to log before/after external call when this is exit point
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", "message extraction threw exception: " + (util.error.isError(e) ? e.description : e), false, "calling failure logic");
						sendFailed(http, e);
						chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages[success]", gadget.logging.exitPoint, false, "message extraction threw exception: " + (util.error.isError(e) ? e.description : e));
						return -1;
					}
				};
				try
				{
					var url = chirpr.twitter.getItemsUrl + "count=" + chirpr.messageCount 
						+ (chirpr.twitter.lastMessage<=0 ? "" : "&since_id="+chirpr.twitter.lastMessage);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", "getting messages: " + url);
					var http = util.http.get(url, chirpr.userName, chirpr.password);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", "http send succeeded", true, "calling success logic");
					var msgCount = sendSuccess(http);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", gadget.logging.exitPoint, (msgCount >= 0), (msgCount == -1) ? "error during http request" : msgCount+" message(s)");
				}
				catch (e)
				{
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", "http send failed: " + (util.error.isError(e) ? "[error] "+e.description : e), false, "calling failure logic");
					sendFailed(null, e);
					chirpr.logUpdate("chirpr", "chirpr.twitter.getMessages", gadget.logging.exitPoint);
				}
			},
			
			addMessage: function(msg)
			{
				// log
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", gadget.logging.entryPoint);
				
				// if offline, return
				if (gadget.host.offline) { chirpr.logPost("chirpr", "chirpr.twitter.addMessage", gadget.logging.exitPoint, true, "gadget offline; no message added"); return true; }
				
				// url encode message -- see also: http://xkr.us/articles/javascript/encode-compare
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", "encoding message: "+msg);
				msg = encodeURIComponent(msg);
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", "encoding complete: "+msg);
				
				// get reply to message id
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", "checking reply status (reply to user="+chirpr.replyToUser+", message="+chirpr.replyToMessage+")");
				var reply = (util.string.isEmpty(chirpr.replyToUser)) ? "" : "&in_reply_to_status_id="+chirpr.replyToMessage;
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", "reply url suffix: "+reply);
				
				// try to make the call and return success/failure
				try
				{
					var url = chirpr.twitter.postItemUrl+"source=chirpr"+reply+"&status="+msg;
					chirpr.logPost("chirpr", "chirpr.twitter.addMessage", "posting message to url: " + url);
					util.http.post(url, chirpr.userName, chirpr.password);
				}
				catch(e)
				{
					chirpr.logPost("chirpr", "chirpr.twitter.addMessage", gadget.logging.exitPoint, false, "add message failed: " + (util.error.isError(e) ? e.description : e));
					return false;
				}
				chirpr.logPost("chirpr", "chirpr.twitter.addMessage", gadget.logging.exitPoint, true, "message created: "+msg);
				return true;
			},
			
			follow: function(user)
			{
				chirpr.logFollow("chirpr", "chirpr.twitter.follow", gadget.logging.entryPoint, true, "user="+user);
				try
				{
					util.http.post(chirpr.twitter.followUrlFormat.replace("{0}", user), chirpr.userName, chirpr.password);
					chirpr.logFollow("chirpr", "chirpr.twitter.follow", gadget.logging.exitPoint, true, "now following "+user);
				}
				catch(e)
				{
					chirpr.logFollow("chirpr", "chirpr.twitter.follow", gadget.logging.exitPoint, false, "not following '"+user+"'; error: "+e.description);
					return false;
				}
				return true;
			}
		},
		
		
		/***************************************************************************
		 * properties
		 *  - hasCredentials : determines if a username and password are known
		 *  - soundEnabled   : determines if a notification sound is enabled
		 **************************************************************************/
		displaySize         : null,
		messageCount        : 20,
		notifySound         : null,  // notification sound disabled by default based on user preference
		project             : "chirpr",
		password            : null,
		refreshInterval     :	60*1000,
		replyToUser         : null,
		replyToMessage      : null,
		userName            : null,
		updateCheckInterval : 24*60*60*1000,  // 24 hours
		hasCredentials      : function()
		{
			if (gadget.host.sidebar)
			{
				chirpr.userName = gadget.settings.get(chirpr.properties.userName);
				chirpr.password = gadget.settings.get(chirpr.properties.password);
			}
			return !(util.string.isEmpty(chirpr.userName) || util.string.isEmpty(chirpr.password));
		},
		soundEnabled        : function()
		{
			chirpr.notifySound = gadget.settings.get(chirpr.properties.notifySound);
			return (chirpr.notifySound != null);
		},
		
		
		/***************************************************************************
		 * chirpr.init : initializes chirpr and calls page.init()
		 **************************************************************************/
		init: function()
		{
			try
			{
				chirpr.logInit("chirpr", "chirpr.init", gadget.logging.entryPoint);
				chirpr.properties.sync();
				gadget.host.setSettings(chirpr.pages.settings);
				chirpr.logInit("chirpr", "chirpr.init", "successful init", true, "calling page.init()");
				page.init();
				chirpr.logInit("chirpr", "chirpr.init", gadget.logging.exitPoint, true);
			}
			catch (e)
			{
				chirpr.logInit("chirpr", "chirpr.init", gadget.logging.exitPoint, false, "error: " + e.description);
				throw(e);
			}
		},


		//***| chirpr.isUpToDate() |********************************************

		isUpdateAvailable: function()
		{
			if (gadget.host.offline) { return false; }
			return util.version.isNewer(gadget.version, util.codeplex.getDefaultVersion(chirpr.project));
		},

		
		/***************************************************************************
		 * chirpr.log : logs an event
		 **************************************************************************/
		log: function(page, func, action, desc, success, outcome)
		{
			var user = (util.string.isEmpty(chirpr.userName) ? null 
				: chirpr.userName + (util.string.isEmpty(chirpr.password) ? "!" : ""));
			return gadget.logging.log(page, func, user, action, desc, success, outcome);
		},
		logInit     : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.init, d, s, o); },
		logUpdate   : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.getMessages, d, s, o); },
		logSettings : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.saveSettings, d, s, o); },
		logAuth     : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.authenticate, d, s, o); },
		logPost     : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.postMessage, d, s, o); },
		logFollow   : function(p,f,d,s,o) { return chirpr.log(p, f, gadget.logging.actions.followUser, d, s, o); }
	};
	util.addHandler(window, "onload", chirpr.init);
	if (gadget.host.sidebar) { document.chirpr = chirpr; }
}